/**
 * 
 */
package roborally.script;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import exception.IllegalScriptSyntaxException;

import roborally.model.*;
import roborally.script.singleCommand.Move;

/**
 * @author r0258417
 *
 */
public class ScriptReader {
	
	public ScriptReader() {}
	
	private static ArrayList<String> extractCommandElements(String string){
		ArrayList<String> result = new ArrayList<String>();
		
		//only remove the extra parentheses with the initial input string.
		//if we do not add this boolean : 
		//when the initial string is finally reduced to it's last block, 
		//the parentheses will be reduced, which will cause the method to add a subblock of this last subblok to the array. 
		boolean extraParentheses = true;
		
		//Zolang de string nog haakjes heeft (en dus geen single command/condition is
		while(string.contains("(")) {
			string = string.trim();
			//System.out.println(string);
			//Begin te zoeken vanaf het begin van de string
			int open = 0;
			int openIndex = 0;
			int close = 0;
			int closeIndex = string.length()-1;
			
			//Valid geeft aan of het eerste haakje gevonden is
			boolean valid = false;
			//Ga over de hele string en tel de geopende en gesloten haakjes
			for(int i = 0; i < string.length(); i++) {
				char c = string.charAt(i);
				if(c == '(') {
					if(!valid) {
						openIndex = i;
						valid = true;
					}
					open++;
				}
				if(c == ')') {
					closeIndex = i;
					close++;
				}
				
				//Als er even veel haakjes geopend en gesloten zijn hebben we een blok. 
				//Als het aantal geopende haakjes gelijk is aan nul moeten we verder blijven zoeken.
				if(open == close && open != 0) {
					
					//Maak een string die alleen de eerste blok bevat.
					String substring = string.substring(openIndex+1, closeIndex).trim();
					
					//Als de volledige string niet start met een haakje, en in een syntactisch correct programma dus met een commando
					//Neem het commando en steek het in het resultaat.
					if(!string.startsWith("(")) {
						extraParentheses = false;
						String[] words = string.split("\\s");
						result.add(words[0]);
					}
					//De volledige string begint met een haakje, ga door
					else {
						if(string.startsWith("(") && openIndex == 0 && closeIndex == string.length()-1 && extraParentheses) {
							string = string.substring(1, string.length()-1).trim();
							break;
						}
					}
					if(!result.isEmpty()) {
						result.add(substring);
					}
					string = string.substring(closeIndex+1, string.length());
					
					break;
				}
			}
		}
		if(!string.trim().isEmpty()){
			result.add(string);
		}
		//System.out.println(result);
		
		return result;
	}
	
	
	public static Node extractNode(String string){
		ArrayList<String> extraction = extractCommandElements(string);
		if(!extraction.isEmpty()) {
			String firstElement = extraction.get(0);
		extraction.remove(0);
		ArrayList<Node> nodes = new ArrayList<ScriptReader.Node>();
		for(String s : extraction){
			if(!s.isEmpty()) {
				nodes.add(extractNode(s));
			}

		}
		ScriptReader reader = new ScriptReader();
		if(nodes.isEmpty()){
			return reader. new LeafNode(firstElement);
		} else {
			Node[] nodeArray = new Node[nodes.size()];
			for(int i = 0 ; i<nodes.size() ; i++){
				nodeArray[i] = nodes.get(i);
			}
			return reader.new TreeNode(firstElement, nodeArray);
		}
		}
		ScriptReader reader = new ScriptReader();
		return reader.new LeafNode("");
		
	}
	
	public static String readTextFile(String name) {
		
		File file = new File(name + ".prog");
		StringBuffer contents = new StringBuffer();
		BufferedReader reader = null;

		try {
			reader = new BufferedReader(new FileReader(file));
			String text = null;

			// repeat until all lines is read
			while ((text = reader.readLine()) != null) {
				contents.append(text.trim()).append(" ");
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (reader != null) {
					reader.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// show file contents here
		//System.out.println(contents.toString());
		return contents.toString();
	}
	
	public static Statement stringToStatement(String commandWord, Robot robot, Statement...statements ){
		Statement result = null;
		String[] words = commandWord.split("\\s+");
		CommandWord rightCommand = null;
		
		ArrayList<Command> commands = new ArrayList<Command>();
		ArrayList<Condition> conditions = new ArrayList<Condition>();
		
		
		for(Statement statement : statements) {
			if(Command.class.isAssignableFrom(statement.getClass())) {
				commands.add((Command) statement);
			}
			if(Condition.class.isAssignableFrom(statement.getClass())) {
				conditions.add((Condition) statement);
			}
		}
		
		Command[] commandArray = new Command[commands.size()];
		Condition[] conditionArray = new Condition[conditions.size()];
		for(int i = 0; i < commands.size(); i++) {
			commandArray[i] = commands.get(i);
		}
		for(int i = 0; i < conditions.size(); i++) {
			conditionArray[i] = conditions.get(i);
		}
		
		
		for(CommandWord comWord : CommandWord.values()){
			if(comWord.toString().startsWith(words[0])){
				rightCommand = comWord;
			}
		}
		try{
			Constructor<?> constructor = rightCommand.getCommandClass().getConstructor(robot.getClass(),String.class);
			result = (Statement) constructor.newInstance(robot,words[1]);
		}catch(Exception e){
			try {
				Constructor<?> constructor = rightCommand.getCommandClass().getConstructor(robot.getClass(), Command[].class);
				result = (Statement) constructor.newInstance(robot, commandArray);
			}
			catch(Exception x) {
				try {
					Constructor<?> constructor = rightCommand.getCommandClass().getConstructor(robot.getClass(), Condition[].class);
					result = (Statement) constructor.newInstance(robot, conditionArray);
				}
				catch(Exception c) {
					try {
						Constructor<?> constructor = rightCommand.getCommandClass().getConstructor(robot.getClass(), Condition.class, Command[].class);
						result = (Statement) constructor.newInstance(robot, conditionArray[0], commandArray);
					}
					catch (Exception p) {
						try {
							Constructor<?> constructor = rightCommand.getCommandClass().getConstructor(robot.getClass(), Condition.class, Command.class);
							result = (Statement) constructor.newInstance(robot, conditionArray[0], commandArray[0]);
						}
						catch (Exception t) {
							try {
								Constructor<?> constructor = rightCommand.getCommandClass().getConstructor(robot.getClass());
								result = (Statement) constructor.newInstance(robot);
							}
							catch(Exception i) {
								i.printStackTrace();
							}
						}
					}
				}
				
				
			}
		}
		
		return result;
	}
	
	public static Command commandFromFileForRobot(String name, Robot robot) {
		String content = ScriptReader.readTextFile(name);
		Statement statement = ScriptReader.extractNode(content).getStatement(robot);
		
		if(!Command.class.isAssignableFrom(statement.getClass())) throw new IllegalScriptSyntaxException("This is not a valid program");
		
		return (Command) statement;
	}
	
	public static String statementToString(Statement statement, int depth) {
		String program = "";
		String indentation = "";
		for(int i = 0; i < depth; i++) {
			indentation += "\t";
		}
		String closed = "";
		for(int i = 0; i < depth -1; i++) {
			closed += "\t";
		}
		
		if(NonConditionalCommand.class.isAssignableFrom(statement.getClass())) {
			NonConditionalCommand nonConCom = (NonConditionalCommand) statement;
			program += "(" + statement.toString();
			
			for(Command c : nonConCom.getCommands()) {
				program += "\n"+ indentation + statementToString(c, depth+1);
			}
			program += "\n" + closed + ")";
		}
		
		if(ConditionalCommand.class.isAssignableFrom(statement.getClass())) {
			ConditionalCommand conCom = (ConditionalCommand) statement;
			program += "(" + statement.toString() + " ";
			
			program += statementToString(conCom.getCondition(), depth+1);
			
			for(Command c : conCom.getCommands()) {
				program +=  "\n" + indentation + statementToString(c, depth+1);
			}
			program += "\n" + closed + ")";
			
		}
		
		if(SingleCommand.class.isAssignableFrom(statement.getClass())) {
			program += "(" + statement.toString() + ")";
		}
		
		if(ComposedCondition.class.isAssignableFrom(statement.getClass())) {
			ComposedCondition compCon = (ComposedCondition) statement;
			program += "(" + statement.toString();
			
			for(Condition c : compCon.getConditions()) {
				program += " " + statementToString(c, depth+1);
			}
			program += ")";
		}
		
		if(SingleCondition.class.isAssignableFrom(statement.getClass())) {
			program += "(" + statement.toString() + ")";
		}
		
		return program;
	}
	
	public abstract class Node {
		protected String string;
		public String toString(){
			return string;
		}
		public abstract Statement getStatement(Robot robot);
	}
	
	public class LeafNode extends Node {
		public LeafNode(String string){
			this.string = string;
		}
		
		@Override
		public String toString(){
			return "("+ string + ") ";
		}
		@Override
		public Statement getStatement(Robot robot) {
			return stringToStatement(string,robot);
		}
		
	}
	
	public class TreeNode extends Node {
		private ArrayList<Node> childNodes;
		
		@Override
		public String toString(){
			String result = "(" + string + " ";
			for(Node node : childNodes){
				result += node.toString();
			}
			return result + ") ";
		}
		
		public TreeNode(String string, Node...nodes){
			this.string = string;
			childNodes = new ArrayList<ScriptReader.Node>();
			for(Node node : nodes){
				childNodes.add(node);
			}
		}

		@Override
		public Statement getStatement(Robot robot) {
			Statement[] statements = new Statement[childNodes.size()];
			int i = 0;
			for(Node childnode : childNodes){
				statements[i] = childnode.getStatement(robot);
				i++;
			}
			return stringToStatement(string, robot, statements);
		}
	}

}
